﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using ITAP.Core.Modelling.Primitives;
using System.Drawing;
using ITAP.Core.Calculations;
using System.Threading;
using ITAP.Core.Modelling;

namespace ITAP.ContourBuilder.Components
{
    public enum ContourElementPoints
    {
        Start,
        End,
        Center,
        None
    }

    [Serializable]
    public class ContourElement : EndedContourComponent
    {
        public ContourElementHook StartHook
        {
            get
            {
                foreach (IContourComponent component in this.innerComponents)
                    if (component is ContourElementHook)
                    {
                        ContourElementHook hook = component as ContourElementHook;
                        if (hook.HookType == ContourElementPoints.Start)
                            return hook;
                    }
                return null;

            }
        }

        public ContourElementHook CenterHook
        {
            get
            {
                foreach (IContourComponent component in this.innerComponents)
                    if (component is ContourElementHook)
                    {
                        ContourElementHook hook = component as ContourElementHook;
                        if (hook.HookType == ContourElementPoints.Center)
                            return hook;
                    }
                return null;

            }
        }

        public ContourElementHook EndHook
        {
            get
            {
                foreach (IContourComponent component in this.innerComponents)
                    if (component is ContourElementHook)
                    {
                        ContourElementHook hook = component as ContourElementHook;
                        if (hook.HookType == ContourElementPoints.End)
                            return hook;
                    }
                return null;
            }
        }

        private double offset;
        /// <summary>
        /// Величина смещения этого элемента для получения нового контура, который будет составлен из 
        /// элементов, полученных в результате их смещения. Смещение больше 0 означает смещение "наружу" 
        /// (при составлении контура против часовой стрелки), смещение меньше 0 означает смещение "внутрь".
        /// </summary>
        public double Offset
        {
            get { return offset; }
            set { offset = value; }
        }

        //private bool modified = false;
        /// <summary>
        /// Если true, то при определении толеранса и соединении по толерансу с другим элементом контура 
        /// конец этого элемента будет перемещён в положение конца другого элемента, между ними создастся 
        /// коннектор и поле Modified будет выставлено в false.
        /// </summary>
        //public bool Modified
        //{
        //    get { return modified; }
        //    set { modified = value; }
        //}

        public ContourElement()
            : base()
        {
            this.InitializeCollections();
            //            this.lockObject = new object();
        }

        private void InitializeCollections()
        {
            this.connectPriorityForEnds = new List<Type>();
        }       

        #region IEndedContourComponent Members

        public override IContourComponent ConnectTo(IContourComponent component, EndType endOfThisComponentToConnect)
        {
            if (!this.acceptedTypesToConnect.Contains(component.GetType()))
                return null;

            if (this.connectedComponents.Contains(component))
                return null;

            switch (endOfThisComponentToConnect)
            {
                case EndType.Start:
                    if (this.StartConnectedComponent == null)
                    {
                        this.connectedComponents.Add(component);
                        this.startConnectedComponent = component;
                        return this;
                    }
                    else
                    {
                        if (this.connectPriorityForEnds.IndexOf(component.GetType()) <
                            this.connectPriorityForEnds.IndexOf(this.StartConnectedComponent.GetType()))
                        {
                            if (!this.RemoveInnerComponent(this.startConnectedComponent))
                                ContourConnectionProvider.Disconnect(this, this.startConnectedComponent);

                            this.startConnectedComponent = component;
                            this.connectedComponents.Add(component);
                            return this;
                        }
                        else
                            return null;
                    }
                case EndType.End:
                    if (this.EndConnectedComponent == null)
                    {
                        this.connectedComponents.Add(component);
                        this.endConnectedComponent = component;
                        return this;
                    }
                    else
                    {
                        if (this.connectPriorityForEnds.IndexOf(component.GetType()) <
                            this.connectPriorityForEnds.IndexOf(this.endConnectedComponent.GetType()))
                        {
                            if (!this.RemoveInnerComponent(this.endConnectedComponent))
                                ContourConnectionProvider.Disconnect(this, this.endConnectedComponent);

                            this.endConnectedComponent = component;
                            this.connectedComponents.Add(component);
                            return this;
                        }
                        else
                            return null;
                    }
            }
            return null;
        }

        private IEndedPrimitive primitive;
        public override IPrimitive Primitive
        {
            get
            {
                return primitive;
            }
            set
            {
                primitive = value as IEndedPrimitive;
            }
        }

        public override double Height
        {
            get
            {
                return this.primitive.Height;
            }
            set
            {
                if (this.Height != 0 && !SimpleEqualProvider.IsEqual(0, value, 2))
                {
                    if (this.IsHalfRound)
                    {
                        double absValue = Math.Abs(value);
                        ModelPoint newStartPoint;
                        ModelPoint newEndPoint;

                        switch (this.SelectedPoint)
                        {
                            case ContourElementPoints.Start:
                                ModelVector endStartVector = new ModelVector(this.EndPoint, this.StartPoint);
                                endStartVector.Length = absValue * 2;
                                newStartPoint = ModelCalculations.GetOffsetPoint(this.EndPoint, endStartVector);
                                this.MoveStartPoint(newStartPoint);
                                break;
                            case ContourElementPoints.Center:
                                Line startEndLine = new Line(this.StartPoint, this.EndPoint);
                                ModelPoint lineCenter = startEndLine.GetPoint(0.5);
                                ModelVector tangent = startEndLine.GetTangent(0.5).Multiply(absValue);
                                newStartPoint = ModelCalculations.GetOffsetPoint(lineCenter, tangent.Rotate(new Angle(Math.PI)));
                                newEndPoint = ModelCalculations.GetOffsetPoint(lineCenter, tangent);
                                this.MoveStartPoint(newStartPoint);
                                this.MoveEndPoint(newEndPoint);
                                break;
                            case ContourElementPoints.End:
                                ModelVector startEndVector = new ModelVector(this.StartPoint, this.EndPoint);
                                startEndVector.Length = absValue * 2;
                                newEndPoint = ModelCalculations.GetOffsetPoint(this.StartPoint, startEndVector);
                                this.MoveEndPoint(newEndPoint);
                                break;
                        }
                    }
                    else
                        this.primitive.Height = value;
                }
                else
                {
                    ModelPoint _startPoint = (ModelPoint)this.StartPoint.Clone();
                    ModelPoint _endPoint = (ModelPoint)this.EndPoint.Clone();

                    if (SimpleEqualProvider.IsEqual(value, 0, 2))
                        this.primitive = new Line(_startPoint, _endPoint);
                    else
                    {
                        // Здесь вшито создание арки, но если потребуется использование других кривых, 
                        // то надо будет вводить тип кривой для ContourElement и создавать объект примитива, 
                        // основываясь на этом типе (например, эллиптическая арка или другая кривая).
                        this.primitive = new Circle(_startPoint, _endPoint, value);
                    }
                }
            }
        }

        public override bool IsHalfRound
        {
            get
            {
                return base.IsHalfRound;
                //if (this.primitive is IEndedCurvedPrimitive)
                //{
                //    IEndedCurvedPrimitive curvedPrimitive = this.primitive as IEndedCurvedPrimitive;
                //    return curvedPrimitive.IsHalfRound;
                //}
                //else
                //{
                //    return false;
                //}
            }
            set
            {
                if (this.primitive is IEndedCurvedPrimitive)
                {
                    IEndedCurvedPrimitive curvedPrimitive = this.primitive as IEndedCurvedPrimitive;
                    curvedPrimitive.IsHalfRound = value;
                }
                else
                {
                    return;
                }
            }
        }

        #endregion

        #region IContourComponent Members

        public override RectangleF BoundingBox
        {
            get
            {
                return this.primitive.BoundingBox;
            }
        }

        public override IContourComponent ConnectToEnded(IEndedContourComponent component, EndType endToConnect)
        {
            // Пока что нет необходимости соединять элемент контура с концом какого-нибудь компонента.
            throw new NotImplementedException();
        }

        public override IContourComponent DisconnectFrom(IContourComponent component)
        {
            if (this.connectedComponents.Remove(component))
            {
                if (component == this.StartConnectedComponent)
                    this.startConnectedComponent = null;
                else if (component == this.EndConnectedComponent)
                    this.endConnectedComponent = null;
                return this;
            }
            else
                return null;
        }

        public override bool IsComponentDotted
        {
            get
            {
                return false;
            }
        }

        public override ModelPoint Position
        {
            get { return this.primitive.GetPoint(0.5); }
        }

        public override void SelectByPoint(ModelPoint point)
        {
            bool hookSelected = false;
            foreach (IContourComponent component in this.InnerComponents)
            {
                if (component is ContourElementHook)
                {
                    if ((component as ContourElementHook).CanSelectByPoint(point))
                    {
                        component.Selected = true;
                        this.selected = true;
                        hookSelected = true;
                    }
                    else
                    {
                        component.Selected = false;
                    }
                }
            }

            if (!hookSelected)
            {
                if (this.CanSelectByPoint(point))
                    this.selected = true;
                else
                    this.selected = false;
            }
        }

        #endregion

        #region IEndPointsMoveable Members

        public override void MoveStartPoint(ModelPoint destination)
        {
            this.primitive.MoveStartPoint(destination);
        }

        public override void MoveEndPoint(ModelPoint destination)
        {
            this.primitive.MoveEndPoint(destination);
        }

        #endregion

        //private ContourElementPoints selectedPoint;

        public ContourElementPoints SelectedPoint
        {
            get 
            {
                foreach (IContourComponent component in this.InnerComponents)
                {
                    if (component is ContourElementHook)
                    {
                        if ((component as ContourElementHook).Selected)
                        {
                            return (component as ContourElementHook).HookType;
                        }
                    }
                }
                return ContourElementPoints.None;
            }
            set 
            {
                foreach (IContourComponent component in this.InnerComponents)
                {
                    if (component is ContourElementHook)
                    {
                        if ((component as ContourElementHook).HookType == value)
                            component.Selected = true;
                        else
                            component.Selected = false;
                    }
                }
            }
        }

        public ModelPoint SelectedPointPosition
        {
            get
            {
                switch (this.SelectedPoint)
                {
                    case ContourElementPoints.Start:
                        return this.primitive.GetPoint(0);
                    case ContourElementPoints.Center:
                        return this.primitive.GetPoint(0.5);
                    case ContourElementPoints.End:
                        return this.primitive.GetPoint(1);
                }
                return null;
            }
        }

        private List<Type> connectPriorityForEnds;

        public List<Type> ConnectPriorityForEnds
        {
            get { return connectPriorityForEnds; }
        }

        #region IContourComponent Members


        public override void AcceptVisitor(IContourVisitor visitor)
        {
            visitor.Visit(this);
        }

        #endregion

        #region IContourVisitor Members

        public override void Visit(ContourElement element)
        {
            if (element == this)
                return;

            double radiusInModel = this.coordinateConverter.ScreenMeasureToModelMeasure(GlobalData.ConnectorRadiusInPixels, screenWidth, screenHeight, modelRectangle);

            EndType nearestElementEndWithStart = element.GetNearestEndPoint(this.StartPoint, 2 * radiusInModel);
            EndType nearestElementEndWithEnd = element.GetNearestEndPoint(this.EndPoint, 2 * radiusInModel);

            ModelPoint pointToInsertConnector = null;

            switch (nearestElementEndWithStart)
            {
                //case EndType.Start:  
                //    // Начало не может коннектиться с началом в контуре.
                //    return;

                case EndType.End:

                    this.componentsInTolerance.Add(element);

                    if (element.EndConnectedComponent != null)
                        return;

                    if (this.StartConnectedComponent != null)
                        return;

                    if (this.Modified)
                    {
                        pointToInsertConnector = element.EndPoint.Clone() as ModelPoint;
                        this.Modified = false;
                    }
                    else
                    {
                        pointToInsertConnector = this.StartPoint.Clone() as ModelPoint;
                        element.Modified = false;
                    }

                    ContourConnector connector = ContourElementFactory.CreateConnector(pointToInsertConnector);
                    ContourConnectionProvider.Connect(this, connector, EndType.Start);
                    ContourConnectionProvider.Connect(element, connector, EndType.End);
                    ContourModel rootModel = this.GetRootModel();
                    rootModel.AddInnerComponent(connector);
                    rootModel.Update();// connector.Update();

                    break;
            }

            switch (nearestElementEndWithEnd)
            {
                case EndType.Start:
                    this.componentsInTolerance.Add(element);

                    if (element.StartConnectedComponent != null)
                        return;

                    if (this.EndConnectedComponent != null)
                        return;

                    if (this.Modified)
                    {
                        pointToInsertConnector = element.StartPoint.Clone() as ModelPoint;
                        this.Modified = false;
                    }
                    else
                    {
                        pointToInsertConnector = this.EndPoint.Clone() as ModelPoint;
                        element.Modified = false;
                    }

                    ContourConnector connector = ContourElementFactory.CreateConnector(pointToInsertConnector);
                    ContourConnectionProvider.Connect(this, connector, EndType.End);
                    ContourConnectionProvider.Connect(element, connector, EndType.Start);
                    ContourModel rootModel = this.GetRootModel();
                    rootModel.AddInnerComponent(connector);
                    rootModel.Update(); // connector.Update();

                    break;
                //case EndType.End:
                //    // Конец не может коннектиться с концом в контуре.
                //    return;
            }


        }

        #endregion


        public override void Visit(ContourConnector connector)
        {
            if(this.connectedComponents.Contains(connector))
                return;

            double radiusInModel = this.coordinateConverter.ScreenMeasureToModelMeasure(GlobalData.ConnectorRadiusInPixels, screenWidth, screenHeight, modelRectangle);

            if (SimpleEqualProvider.IsEqual(this.StartPoint, connector.Position, radiusInModel * 2) && this.startConnectedComponent == null)
            {
                ContourConnectionProvider.Connect(this, connector, EndType.Start);
            }
            else if (SimpleEqualProvider.IsEqual(this.EndPoint, connector.Position, radiusInModel * 2) && this.endConnectedComponent == null)
            {
                ContourConnectionProvider.Connect(this, connector, EndType.End);
            }
        }


        public EndType GetNearestEndPoint(ModelPoint point, double tolerance)
        {
            double startDistance = ModelCalculations.GetDistance(this.StartPoint, point);
            double endDistance = ModelCalculations.GetDistance(this.EndPoint, point);
            if (tolerance == 0)
            {
                if (startDistance < endDistance)
                    return EndType.Start;
                else if (startDistance > endDistance)
                    return EndType.End;
                else
                    return EndType.Both;
            }
            else
            {
                if (startDistance < endDistance && startDistance < tolerance)
                    return EndType.Start;
                else if (startDistance > endDistance && endDistance < tolerance)
                    return EndType.End;
                else if (startDistance == endDistance && startDistance < tolerance)
                    return EndType.Both;
                else
                    return EndType.NotDefined;
            }
        }



        #region IContourComponent Members


        public override void MoveTo(ModelPoint destination)
        {
            switch (this.SelectedPoint)
            {
                case ContourElementPoints.Start:
                    this.MoveStartPoint(destination);
                    break;
                case ContourElementPoints.Center:
                    this.MoveCenterPoint(destination);
                    break;
                case ContourElementPoints.End:
                    this.MoveEndPoint(destination);
                    break;

            }
        }

        public override void MoveWithOffset(ModelVector offset)
        {
            switch (this.SelectedPoint)
            {
                case ContourElementPoints.Start:
                    this.MoveStartPoint(offset);
                    break;
                case ContourElementPoints.Center:
                    this.MoveCenterPoint(offset);
                    break;
                case ContourElementPoints.End:
                    this.MoveEndPoint(offset);
                    break;
            }
        }

        #endregion

        #region IContourComponent Members


        public override IContourComponent ConnectTo(IContourComponent component)
        {
            if (!this.acceptedTypesToConnect.Contains(component.GetType()))
                return null;

            if (this.connectedComponents.Contains(component))
                return null;

            this.connectedComponents.Add(component);
            return this;

        }
        #endregion

        #region ICloneable Members

        public override object Clone()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IContourComponent Members


        public override bool IsInTolerance(ModelPoint point, double tolerance)
        {
            if (this.StartHook.IsInTolerance(point, tolerance) || this.CenterHook.IsInTolerance(point, tolerance) || this.EndHook.IsInTolerance(point, tolerance))
                return true;

            return false;
        }

        #endregion

        #region IEndPointsMoveable Members


        public override void MoveCenterPoint(ModelPoint destination)
        {
            ModelPoint currentPosition = this.Position;
            ModelVector offset = new ModelVector(currentPosition, destination);
            this.MoveCenterPoint(offset);
        }

        #endregion

        #region IEndPointsMoveable Members


        public override void MoveStartPoint(ModelVector offset)
        {
            ContourConnectionProvider.Disconnect(this, this.startConnectedComponent);
            ModelPoint currentPosition = this.SelectedPointPosition;

            ModelPoint point = ModelCalculations.GetOffsetPoint(currentPosition, offset);
            this.MoveStartPoint(point);
        }

        public override void MoveEndPoint(ModelVector offset)
        {
            ContourConnectionProvider.Disconnect(this, this.endConnectedComponent);
            ModelPoint currentPosition = this.SelectedPointPosition;
            ModelPoint point = ModelCalculations.GetOffsetPoint(currentPosition, offset);
            this.MoveEndPoint(point);
        }

        public override void MoveCenterPoint(ModelVector offset)
        {
            this.Height += offset.X + offset.Y;
        }

        #endregion

        #region IContourComponent Members


        public override bool CanSelectByPoint(ModelPoint point)
        {
            foreach (IContourComponent component in this.InnerComponents)
            {
                if (component is ContourElementHook)
                {
                    if ((component as ContourElementHook).CanSelectByPoint(point))
                        return true;
                }
            }

            double distance = this.Primitive.GetDistanceToPoint(point);
            if (distance < 10)
                return true;

            return false;
        }

        #endregion

        //public override bool Selected
        //{
        //    get
        //    {
        //        foreach (IContourComponent innerComponent in this.InnerComponents)
        //            if (innerComponent is ContourElementHook && innerComponent.Selected)
        //                return true;

        //        return false;
        //    }
        //    set
        //    {
        //      //  base.Selected = value;
        //    }
        //}
    }
}
